#! /usr/bin/python

import shlex
import subprocess
import unittest
import os
import inspect

sourceFiles = os.listdir('./test_files')

def getCallingTest():
  return '%sTest' % inspect.stack()[2][3].lstrip('test')

#Used for calling system commands. 
#Returns [stderr output, stdout output, process returncode]
def system(commandString):
  processHandle = subprocess.Popen(commandString, 
                                   stdin=None, 
                                   stdout=subprocess.PIPE, 
                                   stderr=subprocess.PIPE, 
                                   shell=True)
  processOutput = list(processHandle.communicate())
  processOutput.append(processHandle.returncode)

  return processOutput

class TestCDSLib(unittest.TestCase):

  #Compiles the calling function's unittest
  def compile(self):
    sourceFile = '%s.c' % getCallingTest()
    compileOutput = system('gcc -g -Wall -ansi -pedantic '
                           'test_files/%s ../src/cdslib.c'
                           ' -o %s' % (sourceFile, sourceFile.strip('.c')))
    if compileOutput.pop() != 0:
      raise Exception(compileOutput[1])

  #Checks the calling function's unittest for memory leaks using valgrind
  def memcheck(self):
    output = system('valgrind --tool=memcheck ./%s' % getCallingTest())
    summaryIndex = 0

    for i, line in enumerate(output[1].split('\n')):
      if 'HEAP SUMMARY' in line:
        summaryIndex = i
      if 'All heap blocks were freed -- no leaks are possible' in line:
        break
    else:
      raise Exception('%s\n%s\n' % (
            getCallingTest(),
            '\n'.join(output[1].split('\n')[summaryIndex:summaryIndex + 10])))

  #runTests the calling function's unittest, captures output, and returns it
  def runTest(self):
    return system('./%s' % getCallingTest())

  def testArray(self):
    self.compile()
    self.memcheck()
    output = self.runTest()

    stdoutOutput = output[0]
    stderrOutput = output[1]

    self.assertEqual(stderrOutput,
                    'Error: Index 123 out of bounds\n')

    self.assertEqual(stdoutOutput,
                    '0 2 4 6 8 10 12 14 16 18 \n')
      
  def testVector(self):
    self.compile()
    self.memcheck()
    output = self.runTest()

    stdoutOutput = output[0]
    stderrOutput = output[1]

    self.assertEqual(stderrOutput, 
                    'Error: Index 24 out of bounds\n'
                    'Error: Index 100001 out of bounds\n'
                    'Error: Index 20 out of bounds\n'
                    'Error: Index 1001 out of bounds\n')

    self.assertEqual(stdoutOutput, 
      '0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 \n'
      '0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 \n'
      '0 3 6 9 100 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 101 \n'
      '102 0 3 6 9 100 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 101 \n'
      '102 0 3 6 9 103 100 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 101 \n'
      '0 3 6 9 103 100 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 101 \n'
      '0 3 6 9 103 100 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 \n'
      '0 3 6 9 103 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 \n')

  def testChain(self):
    self.compile()
    self.memcheck()
    output = self.runTest()

    stdoutOutput = output[0]
    stderrOutput = output[1]

    self.assertEqual(stderrOutput,
                    'Error: Index 0 out of bounds\n'
                    'Error: Index 0 out of bounds\n'
                    'Error: Index 1000 out of bounds\n'
                    'Error: Index 1000 out of bounds\n'
                    'Error: Index 1000 out of bounds\n'
                    'Error: Index 1000 out of bounds\n')

    self.assertEqual(stdoutOutput,
                    '0 2 4 6 8 10 12 14 16 18 \n'
                    '12 2 4 14 8 10 12 14 16 13 \n'
                    '11 12 2 4 11 14 8 10 12 14 16 13 11 \n'
                    '2 4 11 14 8 10 12 14 16 \n'
                    '100 2 4 11 14 8 10 12 14 16 200 \n'
                    '2 4 11 14 10 12 14 16 \n'
                    '1 \n'
                    '2 \n')

  def testTree(self):
    self.compile()
    self.memcheck()
    output = self.runTest()

    stdoutOutput = output[0]
    stderrOutput = output[1]

    self.assertEqual(stderrOutput, 
                    '')
    
    self.assertEqual(stdoutOutput,
                    '8 9 11 12 13 \n')

  def testHashMap(self):
    self.compile()
    self.memcheck()
    output = self.runTest()

    stdoutOutput = output[0]
    stderrOutput = output[1]
   
    self.assertEqual(stderrOutput,
                    '')

    self.assertEqual(stdoutOutput,
                     '')

  #Delete all unittests binaries
  def tearDown(self):
    for sourceFile in sourceFiles:
      os.system('rm -f %s' % sourceFile.strip('.c'))

if __name__ == '__main__':
  unittest.main()

